/*
*    Phantom v2.0 GUI Automation Software
*    Copyright (C) 2009  John C. Van Arsdall (john@phantomtest.com)
*
*    This program is free software; you can redistribute it and/or modify
*    it under the terms of the GNU General Public License as published by
*    the Free Software Foundation; either version 2 of the License, or
*    (at your option) any later version.
*
*    This program is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU General Public License for more details.
*
*    You should have received a copy of the GNU General Public License along
*    with this program; if not, write to the Free Software Foundation, Inc.,
*    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include "ssi_dataclass.h"
#include "ssi_engine.h"
#include "ssi_dataclass.h"
#include "ssi_internals.h"


CDataClass::CDataClass(String &Name, const CDataClass* InheritFrom) : CData()
, cSuper(SSI_NULL)
, MemberFuncs(SSI_NULL)
, IsExternalCall(0)
, LocSuper(SSI_NULL)
, MyEngine(SSI_NULL)
{
	cSuper = InheritFrom;
	DataName = Name;
	Type = Factory_RegisterData(this);
	MemberVars = new VarTree();						// These need to be deleted upon deregistration
	MemberFuncs = new FuncTree();
	InitClass();
}

CDataClass::CDataClass(const CDataClass& Other) : CData()
, cSuper(SSI_NULL)
, MemberFuncs(SSI_NULL)
, IsExternalCall(0)
, LocSuper(SSI_NULL)
{
	cSuper = Other.cSuper;
	Type = Other.Type;
	DataName = Other.DataName;
	MemberVars = Other.MemberVars;
	MemberFuncs = Other.MemberFuncs;
	//IsExternalCall = Other.IsExternalCall;

	// To create the local member vars and LocSuper
	InitClass();
}

CDataClass::~CDataClass(void)
{

	// MemberVars and MemberFuncs must be deleted!!

	//delete LocMemberVars;  => Is not a pointer
	if(LocSuper){
		delete LocSuper;
		LocSuper = SSI_NULL;
	}
}

CData* CDataClass::MakeData(void)
{
	CDataClass* aClass = new CDataClass(*this);
	return (CData*)aClass;
}

SSI_BOOL CDataClass::DataFromString(const String& sData)
{
	return 0;
}

String CDataClass::GetString(void)
{
	String Mess;
	VarItor TopItor;
	VarItor TempItor;
	CVariable aVar;
	String Ret = "class{ ";
	if(!MemberVars){
	}
	//TopNode = MemberVars->getTop();
	TopItor = MemberVars->begin();
	while(TopItor != MemberVars->end()){
		//TopNode->Get(aVar);
		aVar = *TopItor;
		TempItor = find(LocMemberVars.begin(), LocMemberVars.end(), aVar);
		//TempNode = LocMemberVars.find(aVar);
		if(TempItor != LocMemberVars.end()){
			aVar = *TempItor;
			//TempNode->Get(aVar);
		}

		Ret = Ret+aVar.GetTypeString() + "(" + aVar.Name;
		if(aVar.GetNumElems() > 1){
			Mess.Format("[%d]", aVar.GetNumElems());
			Ret = Ret+Mess;
		}
		Ret = Ret	+ "):";

		Ret = Ret+aVar.Get().GetString();
		if(aVar.GetNumElems() > 1){
			Ret = Ret + "...";
		}
		Ret = Ret + " ";

		++TopItor;
		//TopNode = TopNode->next();
	}
	if(LocSuper){
		Ret = Ret + "super(" + LocSuper->DataName + "):" + LocSuper->GetString();
	}
	Ret = Ret+"}";
	return Ret;
}

SSI_BOOL CDataClass::CopyData(CData* Other)
{
	if(!Other) return 0;
	LocMemberVars = ((CDataClass*)(Other))->LocMemberVars;
	cSuper = ((CDataClass*)(Other))->cSuper;
	//IsExternalCall = ((CDataClass*)(Other))->IsExternalCall;
	return 1;
}

SSI_INT64 CDataClass::SizeOf(void)
{
	// This is never used,
	// since arrays are simply pointers...
	// consider removing from framework
	return 0;
}

SSI_BOOL CDataClass::boolean(void)
{
	return 0;
}

CStatus CDataClass::AddFunction(CFunctionDecl aFunc)
{
	if(!MemberFuncs){
		// Class never registered
		String sErr;
		sErr.Format(ERR01100, this->DataName);
		return CStatus(sERROR, sErr);
	}

	CFunctionDecl Temp = aFunc;

	// WHAT THE HELL?
	//aFunc.

	FuncNode* aFuncPtr = SSI_NULL;
  
	// Need to check against any superclass functions?
	
	//if(parEngine){
  //  return parEngine->AddFunction(Temp);
  //}

	// Hmmm... the only time protected or private members
	// can be accessed is from withing a function.  Can they
	// be accessed from anywhere else?  Me thinks not.
	// Therefore, in function processing, change all accesses
	// to public in the new engine.  Woohoo!

	// ARCK!!!  How to prevent member dataclass functions from
	// accessing private/public data/functions when they are called
	// (or any functions for that matter) from within a member
	// function?  Will a calling class fix this?

	// Parent classes will only add protected or less when 
	// synchronization occurs.


	if(aFuncPtr = MemberFuncs->find(Temp)){
		aFuncPtr->Get(Temp);
		if(Temp.bIsDefined){
			// Function already defined
			String sErr;
			sErr.Format(ERR01101, Temp.Name.Str(), this->DataName.Str());
			return CStatus(sERROR, sErr);
		}
		aFunc.Access = Temp.Access;
		aFunc.Flags = Temp.Flags;
		aFunc.bIsDefined = Temp.bIsDefined;

		aFuncPtr->Set(aFunc);
		return STAT_OK;
	}else{
		aFuncPtr = MemberFuncs->push(Temp);
		if(aFuncPtr) return STAT_OK;
	}
  return CStatus(sERROR, ERR01102);;
}

// Need to have a determined dimension node
CStatus CDataClass::AddNewVariable(CVariable& aVar)
{

	//aVar.Name = "Class";
	//aVar.SetType(CDataString::GetRegisteredType());
	//AddVariable(aVar);

  CStatus RetStat;
  String RetMess;

	if(GetVariable(aVar)){
		RetMess.Format("Variable '%s' already exists in Class '%s'", aVar.Name.Str(), DataName.Str());
    RetStat.Set(sERROR, RetMess);
    return RetStat;
	}else{
		AddVariable(aVar);
		return STAT_SUCCESS;
	}


	/*
  
  VarNode* aNode = MemberVars->find(aVar);
	// Check parent class, too
  if(aNode)
  {
    RetMess.Format("Variable '%s' already exists in Class '%s'", aVar.Name.Str(), DataName.Str());
    RetStat.Set(sERROR, RetMess);
    return RetStat;
  }

	// Reevaluate whether or not you wanna do dis...
	String InitialValue;
	aVar.SetData(InitialValue, SSI_NULL);
  
	aNode = MemberVars->push(aVar);
  if(!aNode)
  {
    // Come up with some other error
		return CStatus(sERROR, ERR01103);
  }
  return STAT_SUCCESS;
	*/
}

CData* CDataClass::operator_equal(CData* InData, OpInfoT* Info)
{
	if(InData->GetType() != Type) return SSI_NULL;
	CDataClass* Other = (CDataClass*)(InData);
	LocMemberVars = Other->LocMemberVars;
	return (CData*)this;
}

SSI_BOOL CDataClass::SynchronizeData(CEngine* anEng)
{
	CStatus RetStat;

	if(!anEng){
		// Need an engine
		return 0;
	}


	// Must be first so current class functions arent added 
	if(LocSuper){
		LocSuper->SynchronizeData(anEng);

		// Must be a child of your super-class's engine
		MyEngine = LocSuper->MyEngine->CreateFunctionEngine();
	}else{
		MyEngine = anEng->CreateFunctionEngine();
	}


	//if(!aStat.Interp){
	//	aStat.Interp = aStat.Engine->GetInterpreter();
	//}
	
	

	FuncNode* fNode = MemberFuncs->getTop();
  CFunctionDecl aFunc;
  while(fNode){
		fNode->Get(aFunc);
		RetStat = MyEngine->AddFunction(aFunc);
		//RetStat = anEng->AddFunction(aFunc);
		if(!RetStat) return 0;
			fNode = fNode->next();
	}
	
	MyEngine->CallingClass = this;
	//CEngine* anEng = aStat.Engine->CreateFunctionEngine();
	//CInterpreter* Interp = anEng->GetInterpreter();
	//CEngine* oldEng = aStat.Engine;
	//Interp->SetEngine(anEng);



  // Consider removing the SynchronizeData function all together
  //  No... dont remove, but only synchronize in case of function call

	//VarNode* aNode;
	//CVariable aVar;
	//CStatus RetStat;

	// Synchronize this data to the engine
	//if(bTo){
		/*
		aNode = LocMemberVars.getTop();

		// Add the class's existing CVariables to the new engine
		while(aNode){
			aNode->Get(aVar);
			RetStat = anEng->AddVariable(aVar);
			if(!RetStat) return SSI_NULL;
			aNode = aNode->next();
		}

		// Need to add all variables, because local vars may not have been created yet
		aNode = MemberVars->getTop();
	
		// Add the class's non-existing CVariables to the new engine
		while(aNode){
			aNode->Get(aVar);
			// If variable doesnt exist yet, add it.
			if(!anEng->GetVariable(aVar, 1)){
				RetStat = anEng->AddVariable(aVar);
				if(!RetStat) return SSI_NULL;
				if(!LocMemberVars.push(aVar)){
					return SSI_NULL;
				}
			}
			aNode = aNode->next();
		}
		*/
		// Add existing functions to the engine
		//FuncNode* fNode = MemberFuncs->getTop();
		//CFunctionDecl aFunc;

		//if(LocSuper){
			// This may need to be moved later

			// PUBLIC AND PROTECTED ONLY, AND SET SUPER'S ENGINE TO THIS ENGINE
			// MUST DO THIS BEFORE THIS CLASS'S FUNCTIONS
		//	LocSuper->SynchronizeData(anEng, bTo);
		//}

	/*
		while(fNode){
			fNode->Get(aFunc);
			RetStat = anEng->AddFunction(aFunc);
			if(!RetStat) return SSI_NULL;
			fNode = fNode->next();
		}
  */
	// Synchronize the engine's data to this
	//}else{
		/*
		aNode = LocMemberVars.getTop();
		while(aNode){
			aNode->Get(aVar);
			if(anEng->GetVariable(aVar, 1)){
				aNode->Set(aVar);
			}
			aNode = aNode->next();
		}
		aNode = MemberVars->getTop();
	
		// Add the class's non-existing CVariables to the new engine
		// Not sure if I really want this part...
		while(aNode){
			aNode->Get(aVar);

			// This will get the parents variables too...
			if(anEng->GetVariable(aVar)){
				// DO SOMETHING HERE
			}
			aNode = aNode->next();
		}
		*/
	//}
	return 1;
}

SSI_BOOL CDataClass::IsFunctionCallOK(CFunctionDecl InDecl, SSI_CHAR MaxAccess)
{

	//if(IsExternalCall){
		//MaxAccess = ACCESS_PUBLIC;
	//}else{
	//	MaxAccess = ACCESS_PRIVATE;
	//}

	if(MemberFuncs){
		if(MemberFuncs->find(InDecl)){
			if(InDecl.Access > MaxAccess){
				return 0;
			}
			return 1;
		}
	}

	if(LocSuper){

		//LocSuper->IsExternalCall = 1;
		//if(IsExternalCall){
		//	return LocSuper->IsFunctionCallOK(InDecl, ACCESS_PUBLIC);
		//}else{
		//	return LocSuper->IsFunctionCallOK(InDecl, ACCESS_PROTECTED);
		//}
	}

	return 0;
}

/* Deprecated
CStatus CDataClass::EvalClass(VarItor InVars, VarTree& InTree, CStatement& aStat)
{
	CStatus RetStat;
	CVariable aVar;
	CDataClass* aClass;


	// Replace with 
	//SynchronizeData(aStat.Engine);
	//SynchronizeData(anEng, 1);
	

	// OK... this will cause problems because:
	//  1) Functions will find variables of highest global context instead of lowest
	//  2) Class variables passed will will also go to the highest global context
	// Need to have the engine created with the calling class variables, then
	//  have the global variables added.  I think this will most closely 
	//  mimic C++.

	// Propose adding all class functions, variables and current values
	//  Then, add all parent class functions/variables (even
	//  variables of the same name).  This will make available 
	//  parent variables for future expansion.  Also note that
	//  this will require a new 'FunctionExists' function that will
	//  not only check function name, but also function parameters.
	//  This new function should be used also whereever functions
	//  are declared.  Existing functions here will default to
	//  the least-global function.

	IsExternalCall = 1;

	SynchronizeData(aStat.Engine);  // Turns out we need this for all

	if(InVars == InTree.end()) return CStatus(sERROR, ERR01104);  // Shouldnt be here because there are no valid variables below the class name
	//if(!InVars) return CStatus(sERROR, ERR01104);  // Shouldnt be here because there are no valid variables below the class name

	++InVars;
	if(InVars != InTree.end()){
	//if(InVars->next()){           // Only if there is another class...
		
		
		//CInterpreter* Interp = MyEngine->GetInterpreter();
		CEngine* oldEng = aStat.Engine;
		//Interp->SetEngine(MyEngine);
		//aStat.Engine = MyEngine;


		aVar = *InVars;
		//InVars->Get(aVar);
		if(!MyEngine->GetVariable(aVar)){
			// Variable not found
			String sErr;
			sErr.Format(ERR01105, aVar.Name.Str());
			return CStatus(sERROR, sErr);
		}
		aClass = (CDataClass*)(&aVar.GetWorkingData());
		if(aClass){
			RetStat = aClass->EvalClass(++InVars, InTree, aStat);
		}else{
			// we want this so can recover OK.
			RetStat = STAT_ERR;  // Internal Error, In think  
		}
		aStat.Engine = oldEng;  // Do I really need this here?
	}else{
		--InVars;
		// OK, so perhaps all protection information should be handled here,
		//  since all class access must happen through this.
		if(aStat.Type == S_GETVAR){
			aVar = *InVars;
			//InVars->Get(aVar);
			//if(RetStat = GetVariable(aVar, MyEngine, ACCESS_PUBLIC)){
			//if(RetStat = GetVariable(aVar)){
			if(GetVariable(aVar)){
				aStat.RetVar = aVar;
			}else{
				MAKEERR1(ERR01109, aVar.Name);
			}
		}else if(aStat.Type == S_FUNCTION){
			// really, what I want to do is pass the information to a 
			//  create_function_engine, which will look for the function,
			//  and based upon how far down the super-class structure we
			//  are, add only the available variables and functions.  This
			//  includes global functions and variables.  This should replace
			//  SynchronizeData

			CEngine* anEng = CreateFunctionEngine(aStat);

			// need to set the interpreter's engine
			//  This is because user-functions use the interpreter
			//  (ie, there is only *one* interpreter)
  		CInterpreter* Interp = anEng->GetInterpreter();
			CEngine* oldEng = aStat.Engine;
			Interp->SetEngine(anEng);

			if(anEng){
				RetStat = anEng->DoFunction(aStat.aFunction);
			}else{
				// Make an error
				return STAT_ERR;  // This is an internal error
			}
			
			// Reset the interpreters engine
			Interp->SetEngine(oldEng);			

			// SynchronizeData(aStat.Engine);
		}else{
			// Make sure synchronization happens before here
			SynchronizeData(aStat.Engine);
      // Must be evaluate in case it is an operator or something
			RetStat = MyEngine->Evaluate(aStat);
		}
	}

	IsExternalCall = 0;

	//Interp->SetEngine(oldEng);
	return RetStat;
}
*/

SSI_BOOL CDataClass::InitClass(void)
{
	LocMemberVars.clear();
	LocMemberVars = *MemberVars;

	if(LocSuper) delete LocSuper;
	if(cSuper){
		LocSuper = new CDataClass(*cSuper);
	}else{
		LocSuper = SSI_NULL;
	}

	return 0;
}
/*
CStatus CDataClass::GetVariable(CVariable& RetVar, CEngine* anEng, INT32 Prot)
{
  VarNode* aNode = SSI_NULL;

	DimNode dNode = SSI_NULL;
	String Name = RetVar.Name;
	String Temp;
	if(dNode = anEng->GetInterpreter()->GetArrayDimensions(Name, Temp)){
		RetVar.Name = Temp;
	}
	aNode = LocMemberVars.find(RetVar);
  if(aNode)
  {
    aNode->Get(RetVar);

		if(RetVar.Access > Prot){
			
			del_array_node(dNode);
			dNode = SSI_NULL;
			return STAT_FAIL;
			// I dont think this error statement will ever get used
			//String sErr;
			//sErr.Format("(%s) Cannot access protected member: '%s'", this->DataName.Str(), RetVar.Name.Str());
			//return CStatus(sERROR, sErr);
		}
		if(dNode){
			RetVar.SetWorkingNode(dNode);
		}else{
			RetVar.SetWorkingNode(SSI_NULL);
		}
    return STAT_OK;
  }
	RetVar.Name = Name;
	if(LocSuper){
		return LocSuper->GetVariable(RetVar, anEng, ACCESS_PROTECTED);
	}
	return STAT_FAIL;
	//String sErr;
  //sErr.Format("Variable not found: '%s'", RetVar.Name.Str());
	//return CStatus(sERROR, sErr);
}
*/

/*
CStatus CDataClass::SetVariable(CVariable InVar, CEngine* InEng, INT32 InAccess)
{
	// DONT NEED INENG BECAUSE ENGINE LOOKS IN ITSELF FOR FUNCTION LOCALS
  VarNode* aNode = SSI_NULL;
  CVariable Temp = InVar;
	aNode = LocMemberVars.find(Temp);

  if(!aNode){
		if(LocSuper){
			return LocSuper->SetVariable(InVar, InEng, ACCESS_PROTECTED);
		}else{
			return STAT_FAIL;
		}
  }

  aNode->Set(InVar);
  return STAT_SUCCESS;
}
*/

CStatus CDataClass::CallFunction(CFunction& aFunc)
{
		CFunctionDecl aDecl;
		aDecl.Name = aFunc.Name;
		CStatus RetStat;
		//CEngine* Temp; 

		if(MyEngine->FindFunctionDecl(aDecl))  // The function *should* be local to class or global
		{
			if(aDecl.Access > ACCESS_PUBLIC){
				return STAT_ERR;
			}
			// Cant just call 'DoFunction' because will enter infinite loop
			// Need CallingClass in CEngine because need access 
			MyEngine->CallingClass = SSI_NULL;
			MyEngine->GetInterpreter()->SetEngine(MyEngine);
			RetStat = MyEngine->DoFunction(aFunc);

			// Need to restore the Interpreter's engine
			return RetStat;
		}else{
			if(LocSuper){
				if(LocSuper->MyEngine->FindFunctionDecl(aDecl)){
					// Handled in Supers CallFunction
					//if(aDecl.Access > ACCESS_PUBLIC){
					//	return STAT_ERR;
					//}
					return LocSuper->CallFunction(aFunc);
				}
			}
		}

		/*
			NumParams = aDecl.getNumParams();

			if(aFunc.GetNumParams() != NumParams)
			{
				RetMess.Format("Function '%s' requires %d parameters", aDecl.Name.Str(), NumParams);
				RetStat.Set(sERROR, RetMess);
				return RetStat;
			}

			if(aDecl.Flags & USER_FUNCTION_TYPE){
				//CEngine* oldEng = this;
				//Interpreter->SetEngine(CallingClass->MyEngine);
				//RetStat = Interpreter->ProcessUserFunction(aFunc, aDecl);
				//Interpreter->SetEngine(oldEng);
				//return RetStat;
			}else{
				return MyEngine->DoFunction(aFunc);
				//CallingClass->MyEngine->CallingClass = SSI_NULL;
				//RetStat = CallingClass->MyEngine->DoFunction(aFunc);
				//CallingClass->MyEngine->CallingClass = CallingClass;
				//return RetStat;
			}


		}
		else
		{
			// MUST ADD FUNCTIONS IN SYNC DATA BECAUSE FINDFUNCTIONDECL WILL FAIL IF YOU DONT (BEFORE THIS FUNCTION)
			// If find function in Engine, then global 

			// May be in a super class...
			if(LocSuper){
				if(LocSuper->MyEngine->FindFunctionDecl(aDecl)){
					return LocSuper->CallFunction(aFunc);
				}
			}
			//RetMess.Format("Function '%s' is not a member of '%s'", aDecl.Name.Str(), CallingClass->DataName.Str());
			//RetStat.Set(sERROR, RetMess);
			//return RetStat;
		}
*/
	String sErr;
	sErr.Format(ERR01106, aDecl.Name.Str());
	return CStatus(sERROR, sErr);
}

CEngine* CDataClass::CreateFunctionEngine(CStatement& aStat)
{
	CFunctionDecl aDecl;
	aDecl.Name = aStat.aFunction.Name;
	if(MyEngine->FindFunctionDecl(aDecl)){
		// this should synchronize both data and 
		//  functions
		return MyEngine->CreateFunctionEngine();
	}else if(LocSuper){
		return LocSuper->CreateFunctionEngine(aStat);
	}else{
		return SSI_NULL;
	}

/*
		if(MyEngine->FindFunctionDecl(aDecl))  // The function *should* be local to class or global
		{
			if(aDecl.Access > ACCESS_PUBLIC){
				return STAT_ERR;
			}
			// Cant just call 'DoFunction' because will enter infinite loop
			// Need CallingClass in CEngine because need access 
			MyEngine->CallingClass = SSI_NULL;
			MyEngine->GetInterpreter()->SetEngine(MyEngine);
			RetStat = MyEngine->DoFunction(aFunc);

			// Need to restore the Interpreter's engine
			return RetStat;
		}else{
			if(LocSuper){
				if(LocSuper->MyEngine->FindFunctionDecl(aDecl)){
					// Handled in Supers CallFunction
					//if(aDecl.Access > ACCESS_PUBLIC){
					//	return STAT_ERR;
					//}
					return LocSuper->CallFunction(aFunc);
				}
			}
		}
*/


	//return SSI_NULL;
}


SSI_INT64 CDataClass::Register(SSI_INT64 NewType){
	Type = NewType;
	return 1;
}

void CDataClass::UnRegister(){
	Type = 0;
}



